/*==========V4L2框架实现视频拍照的基础逻辑
1、打开设备 int fd = open(“/dev/video0”, O_RDWR);
2、取得设备的属性信息，查看设备具有什么功能，比如是否具有视频输入，或者音频输入输出等
   struct v4l2_capability VIDIOC_QUERYCAP 
3、选择视频输入   struct v4l2_input  VIDIOC_S_INPUT
4、设置视频的制式和帧格式包括宽度和高度等
5、申请帧缓冲区 VIDIOC_REQBUFS  struct v4l2_requestbuffers
6、将申请到的内核空间帧缓冲区映射到用户空间 VIDIOC_QUERYBUF  struct v4l2_buffer
7、将申请到的缓冲区进入视频采集输入队列 VIDIOC_QBUF struct v4l2_buffer
8、开始视频采集 VIDIOC_STREAMON  enum v4l2_buf_type
9、取出视频输出队列的帧缓冲数据，对数据处理 VIDIOC_DQBUF struct v4l2_buffer
10、将帧缓冲区重新进入视频采集输入队列，这样便可循环采集帧数据 VIDIOC_QBUF
11、停止视频采集，解除映射 VIDIOC_STREAMOFF
12、关闭视频设备 close(fd)
*/
 
#include "v4l2_csi.h"
#include <linux/i2c.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

int fd;
int file_fd;
int frame_size;
static Video_Buffer * buffer = NULL;
FILE * file = NULL;
int ioctl_(int fd, int request, void *arg)
{
	int ret = 0;
	do{
		ret = ioctl(fd, request, arg);
	}while(ret == -1 && ret == EINTR);
		
}
//打开设备节点 ：device_name video*节点
int open_device(const char * device_name)
{
	struct stat st;
    if( -1 == stat( device_name, &st ) )
    {
        printf( "Cannot identify '%s'\n" , device_name );
        return -1;
    }

    if ( !S_ISCHR( st.st_mode ) )
    {
        printf( "%s is no device\n" , device_name );
        return -1;
    }

    fd = open(device_name, O_RDWR | O_NONBLOCK , 0);
    if(fd < 0){
        printf("打开摄像头设备失败");
        return -1;
    }
    return 0;	
}


// 设置相机参数//查询设备信息
int init_device(void)
{
	struct v4l2_capability cap;
	/*.获取摄像头的能力 (VIDIOC_QUERYCAP：是否支持视频采集、内存映射等) */
	if (ioctl_(fd, VIDIOC_QUERYCAP, &cap) == -1)
	{
		perror("VIDIOC_QUERYCAP");
		return -1;
	}

	printf("---------------------LINE:%d\n", __LINE__);
	printf("DriverName:%s\nCard Name:%s\nBus info:%s\nDriverVersion:%u.%u.%u\n",
		cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xFF,(cap.version>>8)&0xFF,(cap.version)&0xFF);	
 
	/* 4.枚举摄像头支持的格式           (VIDIOC_ENUM_FMT：MJPG、YUYV等)
      列举出每种格式下支持的分辨率      (VIDIOC_ENUM_FRAMESIZES) */
	struct v4l2_fmtdesc fmtdesc;
	memset(&fmtdesc, 0, sizeof(fmtdesc));
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  //设置视频采集设备类型
    int i = 0;
	while(1){
        fmtdesc.index = i++;
        // 获取支持格式
        if(0 == ioctl_(fd, VIDIOC_ENUM_FMT, &fmtdesc)){
            printf("支持格式：%s, %c%c%c%c\n", fmtdesc.description,
                                            fmtdesc.pixelformat & 0xff,
                                            fmtdesc.pixelformat >> 8 & 0xff,
                                            fmtdesc.pixelformat >> 16 & 0xff,
                                            fmtdesc.pixelformat >> 24 & 0xff);
            // 列出该格式下支持的分辨率             VIDIOC_ENUM_FRAMESIZES & 默认帧率 VIDIOC_G_PARM
            // 1.默认帧率
            struct v4l2_streamparm streamparm;
            streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            if(0 == ioctl_(fd, VIDIOC_G_PARM, &streamparm))
                printf("该格式默认帧率 %d fps\n", streamparm.parm.capture.timeperframe.denominator);
            // 2.循环列出支持的分辨率
            struct v4l2_frmsizeenum frmsizeenum;
            frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            frmsizeenum.pixel_format = fmtdesc.pixelformat;   //设置成对应的格式
            int j = 0;
            printf("支持的分辨率有：\n");
            while(1){
                frmsizeenum.index = j++;
                if(0 == ioctl_(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum))
                    printf("%d x %d\n", frmsizeenum.discrete.width, frmsizeenum.discrete.height);
                else break;
            }
            printf("\n");
        }else break;
    }

	/* 5.设置摄像头类型为捕获、设置分辨率、视频采集格式 (VIDIOC_S_FMT) */
	struct v4l2_format fmt;
	CLEAN(fmt);
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* 视频采集 */
	fmt.fmt.pix.width = WIDTH;				/* 宽 */
	fmt.fmt.pix.height = HEIGHT;			 /* 高 */
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; /* 设置输出类型：MJPG */
//	fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
	if (ioctl_(fd, VIDIOC_S_FMT, &fmt) <0)
	{
		printf("VIDIOC_S_FMT IS ERROR! LINE:%d\n",__LINE__);
		printf("设置摄像头参数失败！");
		return -1;
	}
	else {
		printf("设置摄像头ok \n");
	}
	return 0;
}

//以及获取图像
 int init_mmap()
{
	printf("init_mmap\n");
	/*6.向内核申请内存 (VIDIOC_REQBUFS：个数、映射方式为mmap)将申请到的缓存加入内核队列 (VIDIOC_QBUF)将内核内存映射到用户空间 (mmap)*/
	struct v4l2_requestbuffers req;			
	CLEAN(req);								/*删除文本中所有不能打印的字符*/	
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	/*设置为视频流格式*/
	req.count = 4;							/*申请缓存个数*/
	req.memory = V4L2_MEMORY_MMAP;  		/*使用内存映射缓冲区,申请为物理连续的内存空间*/
	//申请4个帧缓冲区，在内核空间中
	if(0 == ioctl_(fd, VIDIOC_REQBUFS, &req)){
		/*获取每个帧信息，并映射到用户空间 赋值指针防止内存泄漏*/
		buffer = (Video_Buffer *)calloc(req.count, sizeof(Video_Buffer));
		if (buffer == NULL){
			printf("calloc is error! LINE:%d\n",__LINE__);
			return -1;
		}
		struct v4l2_buffer v4l_buffer; 
		int i = 0;
		/* 申请到内存后 */
		for(int i = 0; i < req.count; i++){
			CLEAN(v4l_buffer);	
			/* 将申请到的缓存加入内核队列 (VIDIOC_QBUF)*/
			v4l_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			v4l_buffer.index = i;
			v4l_buffer.memory = V4L2_MEMORY_MMAP;
			if (ioctl_(fd, VIDIOC_QBUF, &v4l_buffer) == 0){
			buffer[i].length = v4l_buffer.length;
			buffer[i].start =(char*) mmap(NULL,v4l_buffer.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,v4l_buffer.m.offset);//由内核分配映射的起始地址、长度、可读写、可共享
			}
		}
	}
	else{
        perror("申请内存失败！");
        return -5;
    }
}

void start_stream()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl_(fd, VIDIOC_STREAMON, &type) == -1){
		printf("VIDIOC_STREAMON IS ERROR! LINE:%d\n", __LINE__);
		printf("打开视频流失败！");
		exit(EXIT_FAILURE);
	}
}

void end_stream()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl_(fd, VIDIOC_STREAMOFF, &type) == -1){
		printf("VIDIOC_STREAMOFF IS ERROR! LINE:%d\n", __LINE__);
		exit(EXIT_FAILURE);
	}
}

static int read_frame()//获取摄像头数据
{
	struct v4l2_buffer buf;
	//Video_Buffer bufData;
	FrameBuffer framebuf,bufData;
	int ret = 0;
	CLEAN(buf);
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	if (ioctl_(fd, VIDIOC_DQBUF, &buf) == -1){	
		printf("VIDIOC_DQBUF! LINEL:%d\n", __LINE__);
		return -1;
	}
	/*往文件节点中添加数据，可以在此处添加socket发送数据*/
	memcpy(framebuf.buf,(unsigned char *)buffer[buf.index].start,buf.bytesused);//bytesused 表示buf中已经使用的字节数
	ret=buf.bytesused;

	socket_send(framebuf.buf,ret);   //发送数据

	if (ioctl_(fd, VIDIOC_QBUF, &buf) == -1){
		printf("VIDIOC_QBUF! LINE:%d\n", __LINE__);
		return -1;
	}
	return 0;
}


int open_file(const char * file_name)
{
	
	file_fd = open(file_name, O_RDWR | O_CREAT, 0777);
	if (file_fd == -1)
	{
		printf("open file is error! LINE:%d\n", __LINE__);
		return -1;
	}
}

void close_mmap()
{
	int i = 0;
	for (i = 0; i < 4 ; i++)
	{
		munmap(buffer[i].start, buffer[i].length);
	}
	free(buffer);
}
void close_device()
{
	close(fd);
	close(file_fd);
}
int process_frame()
{
	usleep(9000);
	read_frame();
}

